/**************************************************************************
* This file is part of Hawkengine.
*
* Hawkengine is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published
* by the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Hawkengine is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Hawkengine.  If not, see <http://www.gnu.org/licenses/>.
**************************************************************************/


/**************************************************************************
* Dependencies
**************************************************************************/
#include "engine/font.h"
#include "engine/image.h"
#include "engine/render.h"

/**************************************************************************
* Preprocessing
**************************************************************************/
#define MIN_CHAR ' '

/**************************************************************************
* Types
**************************************************************************/
typedef struct {
  int num_glyphs;
  int charset;
  float width;
  float height;
  float padding_h;
  float padding_v;
  float glyph_height;
  float font_height;
  int glyphs_per_line_incremental[6];
  float glyph_widths[256];
  float glyph_locations[256];
  float glyph_origins[256];
  float glyph_advancements[256];
  UINT gl_texture;
} font_params_type;

/**************************************************************************
* Variables
**************************************************************************/
extern const file_data_type font_chicago_png;
color_type font_shadow_color = {0, 0, 0, 255};
BOOL font_use_shadow = TRUE;

/**************************************************************************
* Static Variables
**************************************************************************/
static font_params_type current_font_params;
static float average_char_width;
static int char_vertices_offset[256];

/**************************************************************************
* Function Prototypes
**************************************************************************/
static void get_chicago_font_params(font_params_type* font_params);
static float get_text_width(const char* text);
static float get_word_width(const char* text);
static void render_char(const char c);
static float scale_from_size(float size);
static void setup_char_vertices(void);

/**************************************************************************
* Function Implementations
**************************************************************************/
int font_count_lines(const char* text, int* max_width)
{
  const char* temp;
  int num_lines, text_width;

  num_lines = 1;
  if (max_width) {
    (*max_width) = 0;
  }
  text_width = 0;
  temp = text;
  while (*temp) {
    if ((*temp) == '\n') {
      if (max_width) {
        _UPDATE_MAX((*max_width), text_width);
      }
      text_width = 0;
      num_lines++;
    } else {
      text_width++;
    }
    temp++;
  }
  if (max_width) {
    _UPDATE_MAX((*max_width), text_width);
  }
  return num_lines;
}

void font_init(void)
{
  int i;
  UINT texture;

  texture = image_texture_from_data(&font_chicago_png);
  if (texture <= 0) {
    return;
  }

  // get the font data
  get_chicago_font_params(&current_font_params);
  current_font_params.gl_texture = texture;

  // guess char width (to help with centering)
  average_char_width = 0;
  for (i = 0; i < current_font_params.num_glyphs; i++) {
    average_char_width += current_font_params.glyph_advancements[i];
  }
  average_char_width /= current_font_params.num_glyphs;

  // setup all vertices
  setup_char_vertices();
}

void font_render_float(float value, UCHAR leading, UCHAR trailing,
                  float size, float x, float y)
{
  static char format[20];
  static char temp[20];
  static char text[20];

  if (trailing > 0) {
    strcpy(format, "%0");
    sprintf(temp, "%d.%df", leading, trailing);
    strcat(format, temp);
    sprintf(text, format, value);
  } else {
    strcpy(format, "%0");
    sprintf(temp, "%dd", leading);
    strcat(format, temp);
    sprintf(text, format, (int)value);
  }

  // render the text
  font_render_text(text, size, x, y);
}

void font_render_int(int value, UCHAR leading, float size, float x, float y)
{
  font_render_float(value, leading, 0, size, x, y);
}

void font_render_text(const char* text, float size, float x, float y)
{
  font_render_text_aligned(text, size, x, y, ALIGN_CENTER, ALIGN_CENTER);
}

void font_render_text_aligned(const char* text, float size, float x, float y,
                              align_type align_x, align_type align_y)
{
  int i, line, num_lines;
  float scale;
  float delta_x;

  num_lines = font_count_lines(text, NULL);

  // bind the texture
  glEnable(GL_TEXTURE_2D);
  glBindTexture(GL_TEXTURE_2D, current_font_params.gl_texture);

  // center x, y... offset (- width of text + 1 char) / 2.0
  scale = scale_from_size(size);
  glPushMatrix();
  glTranslatef(x, y, 0);
  glScalef(scale, scale, 1);

  // set starting point vertically
  switch (align_y) {
  case ALIGN_CENTER:
    glTranslatef(0, (num_lines - 1.0) / 2.0 * current_font_params.font_height, 0);
    break;
  case ALIGN_BOTTOM:
    glTranslatef(0, (num_lines - 0.5) * current_font_params.font_height, 0);
    break;
  default:
    glTranslatef(0, -0.5 * current_font_params.font_height, 0);
    break;
  }

  glPushMatrix();
  i = -1;
  line = 0;
  while((i == -1) || text[i]) {
    if ((i == -1) || (text[i] == '\n')) {
      // start a new line below
      glPopMatrix();
      glPushMatrix();
      glTranslatef(average_char_width / 2.0, 0, 0);
      switch (align_x) {
      case ALIGN_CENTER:
        glTranslatef(-(get_text_width(text + i + 1) / 2.0), 0, 0);
        break;
      case ALIGN_RIGHT:
        glTranslatef(-get_text_width(text + i + 1), 0, 0);
        break;
      default:
        break;
      }
      if (i != -1) {
        // new line
        line++;
        glTranslatef(0, -1 * current_font_params.font_height * line, 0);
      }
    } else {
      // render normal character
      render_char(text[i]);
      delta_x = current_font_params.glyph_advancements[text[i] - MIN_CHAR];
      glTranslatef(delta_x, 0, 0);
    }

    i++;
  }
  glPopMatrix();
  glPopMatrix();

  glDisable(GL_TEXTURE_2D);
}

void font_render_text_wrapped(const char* text, float size, float x, float y,
                              float width)
{
  int i;
  float scale;
  float delta_x;
  float temp_x;

  // bind the texture
  glEnable(GL_TEXTURE_2D);
  glBindTexture(GL_TEXTURE_2D, current_font_params.gl_texture);

  scale = scale_from_size(size);
  glPushMatrix();
  glTranslatef(x, y, 0);
  glScalef(scale, scale, 1);
  glTranslatef(average_char_width / 2.0,
               -1.0 * current_font_params.font_height / 2.0, 0);

  width /= scale;

  i = 0;
  temp_x = 0;
  glPushMatrix();
  while(text[i]) {
    // check for a line break, if word doesn't fit go to next line
    if (((temp_x + get_word_width(&(text[i]))) >= width) || (text[i] == '\n')) {
      glPopMatrix();
      glTranslatef(0, -1 * current_font_params.font_height, 0);
      glPushMatrix();
      temp_x = 0;
    }
    // render the character if it's valid
    if (text[i] != '\n') {
      render_char(text[i]);
      delta_x = current_font_params.glyph_advancements[text[i] - MIN_CHAR];
      temp_x += delta_x;
      glTranslatef(delta_x, 0, 0);
    }

    i++;
  }
  glPopMatrix();
  glPopMatrix();

  glDisable(GL_TEXTURE_2D);
}

static void get_chicago_font_params(font_params_type* font_params)
{
  int glyphs_per_line_incremental[] = {
    19, 34, 49, 64, 80, 95
  };

  float glyph_widths[] = {
    0.000000, 4.662109, 7.000000, 18.662109, 11.662109, 21.000000, 18.662109,
    2.337891, 7.000000, 7.000000, 11.935547, 11.662109, 4.662109, 11.662109,
    4.662109, 11.662109, 14.000000, 7.000000, 14.000000, 14.000000, 16.337891,
    14.000000, 14.000000, 14.000000, 14.000000, 14.000000, 4.662109, 4.662109,
    12.837891, 14.000000, 12.837891, 14.000000, 21.000000, 14.000000, 14.000000,
    14.000000, 14.000000, 11.662109, 11.662109, 14.000000, 14.000000, 4.662109,
    14.000000, 16.337891, 11.662109, 23.337891, 16.337891, 14.000000, 14.000000,
    14.000000, 14.000000, 11.662109, 14.000000, 14.000000, 14.000000, 23.337891,
    14.000000, 14.000000, 14.000000, 7.000000, 11.662109, 7.000000, 14.000000,
    18.662109, 4.675781, 14.000000, 14.000000, 11.662109, 14.000000, 14.000000,
    11.662109, 14.000000, 14.000000, 4.662109, 11.662109, 16.337891, 4.662109,
    23.337891, 14.000000, 14.000000, 14.000000, 14.000000, 11.662109, 11.662109,
    9.337891, 14.000000, 14.000000, 23.337891, 14.000000, 14.000000, 14.000000,
    7.000000, 2.324219, 7.000000, 14.000000
  };

  float glyph_locations[] = {
    0.000000, 3.063063, 10.788236, 20.851299, 42.576469, 57.301643, 81.364708,
    103.089882, 108.490837, 118.553902, 128.616959, 143.615570, 158.340744,
    166.065918, 180.791092, 188.516266, 203.241440, 220.304504, 230.367569,
    0.000000, 17.063063, 36.464016, 53.527077, 70.590141, 87.653206, 104.716270,
    121.779335, 129.504501, 137.229675, 153.130630, 170.193695, 186.094650,
    203.157715, 227.220779, 0.000000, 17.063063, 34.126125, 51.189186, 65.914360,
    80.639534, 97.702599, 114.765663, 122.490837, 139.553894, 158.954849,
    173.680023, 200.080978, 219.481934, 236.544998, 0.000000, 17.063063,
    34.126125, 48.851295, 65.914360, 82.977425, 100.040489, 126.441444,
    143.504501, 160.567566, 177.630630, 187.693695, 202.418869, 212.481934,
    229.544998, 0.000000, 7.738844, 24.801907, 41.864967, 56.590141, 73.653206,
    90.716270, 105.441444, 122.504509, 139.567566, 147.292740, 162.017914,
    181.418869, 189.144043, 215.544998, 232.608063, 0.000000, 17.063063,
    34.126125, 48.851295, 63.576469, 75.977425, 93.040489, 110.103554,
    136.504501, 153.567566, 170.630630, 187.693695, 197.756760, 203.144043,
    213.207108
  };

  float glyph_origins[] = {
    0.000000, 4.662109, 4.662109, 2.337891, 2.337891, 2.337891, 2.337891, 2.337891,
    2.337891, 2.337891, 2.201172, 2.337891, 2.337891, 2.337891, 2.337891, 2.337891,
    2.337891, 4.662109, 2.337891, 2.337891, 0.000000, 2.337891, 2.337891, 2.337891,
    2.337891, 2.337891, 2.337891, 2.337891, 0.464844, 2.337891, 0.697266, 2.337891,
    2.337891, 2.337891, 2.337891, 2.337891, 2.337891, 2.337891, 2.337891, 2.337891,
    2.337891, 4.662109, 0.000000, 2.337891, 2.337891, 2.337891, 2.337891, 2.337891,
    2.337891, 2.337891, 2.337891, 2.337891, 0.000000, 2.337891, 2.337891, 2.337891,
    2.337891, 2.337891, 2.337891, 2.337891, 2.337891, 2.337891, 2.337891, 0.000000,
    4.662109, 2.337891, 2.337891, 2.337891, 2.337891, 2.337891, 2.337891, 2.337891,
    2.337891, 2.337891, 0.000000, 2.337891, 2.337891, 2.337891, 2.337891, 2.337891,
    2.337891, 2.337891, 2.337891, 2.337891, 2.337891, 2.337891, 2.337891, 2.337891,
    2.337891, 2.337891, 2.337891, 2.337891, 4.662109, 2.337891, 2.337891
  };

  float glyph_advancements[] = {
    9.337891, 14.000000, 16.337891, 23.337891, 16.337891, 25.662109, 23.337891,
    7.000000, 11.662109, 11.662109, 16.337891, 16.337891, 9.337891, 16.337891,
    9.337891, 16.337891, 18.662109, 18.662109, 18.662109, 18.662109, 18.662109,
    18.662109, 18.662109, 18.662109, 18.662109, 18.662109, 9.337891, 9.337891,
    14.000000, 18.662109, 14.000000, 18.662109, 25.662109, 18.662109, 18.662109,
    18.662109, 18.662109, 16.337891, 16.337891, 18.662109, 18.662109, 14.000000,
    16.337891, 21.000000, 16.337891, 28.000000, 21.000000, 18.662109, 18.662109,
    18.662109, 18.662109, 16.337891, 14.000000, 18.662109, 18.662109, 28.000000,
    18.675781, 18.662109, 18.662109, 11.662109, 16.337891, 11.662109, 18.662109,
    18.662109, 14.000000, 18.662109, 18.662109, 16.337891, 18.662109, 18.662109,
    14.000000, 18.662109, 18.662109, 9.337891, 14.000000, 18.662109, 9.337891,
    28.000000, 18.662109, 18.662109, 18.662109, 18.662109, 14.000000, 16.337891,
    14.000000, 18.662109, 18.662109, 28.000000, 18.662109, 18.662109, 18.662109,
    11.662109, 11.662109, 11.662109, 18.662109
  };

  font_params->num_glyphs                    = 95;
  font_params->charset                       = 0;
  font_params->width                         = 256.000000;
  font_params->height                        = 256.000000;
  font_params->padding_h                     = 1.531532;
  font_params->padding_v                     = 3.693694;
  font_params->glyph_height                  = 35.0;
  font_params->font_height                   = 28.0;

  memcpy(font_params->glyphs_per_line_incremental, glyphs_per_line_incremental,
         sizeof(font_params->glyphs_per_line_incremental));
  memcpy(font_params->glyph_widths, glyph_widths,
         sizeof(font_params->glyph_widths));
  memcpy(font_params->glyph_locations, glyph_locations,
         sizeof(font_params->glyph_locations));
  memcpy(font_params->glyph_origins, glyph_origins,
         sizeof(font_params->glyph_origins));
  memcpy(font_params->glyph_advancements, glyph_advancements,
         sizeof(font_params->glyph_advancements));
}

static float get_text_width(const char* text)
{
  int i;
  float text_width;

  // calculate the width of the text until a new line
  text_width = 0;
  i = 0;
  while(text[i] && (text[i] != '\n')) {
    text_width += current_font_params.glyph_advancements[text[i] - MIN_CHAR];
    i++;
  }

  return text_width;
}

static float get_word_width(const char* text)
{
  int i;
  float text_width;

  // calculate the width of the text;
  text_width = 0;
  i = 0;
  while(text[i]) {
    if (isspace(text[i])) {
      break;
    }
    text_width += current_font_params.glyph_advancements[text[i] - MIN_CHAR];
    i++;
  }

  return text_width;
}

static void render_char(const char c)
{
  static float last_color[4];

  glEnableClientState(GL_VERTEX_ARRAY);
  glEnableClientState(GL_TEXTURE_COORD_ARRAY);

  if (font_use_shadow) {
    glGetFloatv(GL_CURRENT_COLOR, last_color);
    render_set_color(&font_shadow_color);
    glPushMatrix();
    glTranslatef(2, -2, 0);
    glDrawArrays(GL_TRIANGLE_FAN, char_vertices_offset[(int)c - MIN_CHAR], 4);
    glPopMatrix();
    glColor4f(last_color[0], last_color[1], last_color[2], last_color[3]);
  }

  glDrawArrays(GL_TRIANGLE_FAN, char_vertices_offset[(int)c - MIN_CHAR], 4);
  glDisableClientState(GL_VERTEX_ARRAY);
  glDisableClientState(GL_TEXTURE_COORD_ARRAY);
}

static float scale_from_size(float size)
{
  return (size / current_font_params.glyph_height);
}

static void setup_char_vertices(void)
{
  static const int MAX_CHAR_CODE = 255;
  int char_code, char_idx, char_grid_idx[2];
  float* vertices;
  float* textures;
  float* char_texture_start;
  float* char_vertex_start;
  float tx_start, tx_end, ty_start, ty_end;
  float x, width, height;
  int base_offset;
  int num_points;

  num_points = (MAX_CHAR_CODE - MIN_CHAR) * 4;

  vertices = malloc(num_points * 3 * sizeof(float));
  textures = malloc(num_points * 3 * sizeof(float));

  // setup each chars vertices
  for (char_code = MIN_CHAR; char_code < MAX_CHAR_CODE; char_code++) {
    char_idx = (char_code - MIN_CHAR);
    char_vertices_offset[char_idx] = (char_idx * 4);

    char_grid_idx[X] = (char_code - MIN_CHAR);
    char_grid_idx[Y] = 0;
    while(char_grid_idx[X] >= current_font_params.glyphs_per_line_incremental[char_grid_idx[Y]]) {
      char_grid_idx[Y]++;
    }

    tx_start = (current_font_params.glyph_locations[char_grid_idx[X]] /
                current_font_params.width);
    tx_end = (tx_start + ((current_font_params.glyph_widths[char_grid_idx[X]] +
                           current_font_params.padding_h * 2) /
                          current_font_params.width));

    ty_start = (1 - (((current_font_params.glyph_height +
                       (current_font_params.padding_v * 2)) *
                      (char_grid_idx[Y] + 1)) /
                     current_font_params.height));
    ty_end = (1 - (((current_font_params.glyph_height +
                     (current_font_params.padding_v * 2)) *
                    char_grid_idx[Y]) /
                   current_font_params.height));

    char_texture_start = (textures + (char_vertices_offset[char_idx] * 3));
    char_vertex_start = (vertices + (char_vertices_offset[char_idx] * 3));

    // set texture coordinates
    *(char_texture_start) = tx_start;
    *(char_texture_start + 1) = 1 - ty_start;
    *(char_texture_start + 2) = 0;
    *(char_texture_start + 3) = tx_end;
    *(char_texture_start + 4) = 1 - ty_start;
    *(char_texture_start + 5) = 0;
    *(char_texture_start + 6) = tx_end;
    *(char_texture_start + 7) = 1 - ty_end;
    *(char_texture_start + 8) = 0;
    *(char_texture_start + 9) = tx_start;
    *(char_texture_start + 10) = 1 - ty_end;
    *(char_texture_start + 11) = 0;

    x = current_font_params.glyph_origins[char_grid_idx[X]] - (average_char_width / 2.0);
    width = (current_font_params.glyph_widths[char_grid_idx[X]] +
             (current_font_params.padding_h * 2));
    height = (current_font_params.glyph_height +
              (current_font_params.padding_v * 2));

    *(char_vertex_start) = x;
    *(char_vertex_start + 1) = (height / 2.0);
    *(char_vertex_start + 2) = 0;
    *(char_vertex_start + 3) = x + width;
    *(char_vertex_start + 4) = (height / 2.0);
    *(char_vertex_start + 5) = 0;
    *(char_vertex_start + 6) = x + width;
    *(char_vertex_start + 7) = -(height / 2.0);
    *(char_vertex_start + 8) = 0;
    *(char_vertex_start + 9) = x;
    *(char_vertex_start + 10) = -(height / 2.0);
    *(char_vertex_start + 11) = 0;
  }

  // add the vertices
  base_offset = render_add_vertices(num_points, vertices, NULL, textures, NULL);

  // setup vertex buffer offsets for each character
  for (char_code = 0; char_code < 256; char_code++) {
    char_vertices_offset[char_code] += base_offset;
  }

  free(vertices);
  free(textures);
}
